package next.fire.spinus.logx.provider.huawei.client;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import next.fire.spinus.logx.base.AbstractClient;
import next.fire.spinus.logx.provider.huawei.conf.HuaweiLogConfig;
import next.fire.spinus.logx.provider.huawei.conf.HuaweiLogConfigHolder;
import next.fire.spinus.logx.utils.codec.ByteHelper;
import next.fire.spinus.logx.utils.codec.HmacSHA256;
import next.fire.spinus.logx.utils.codec.URLEncodeHelper;
import next.fire.spinus.logx.utils.http.FireHttpUtils;
import next.fire.spinus.logx.utils.text.StringEscapeUtils;

import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.TreeMap;

/**
 * Created by daibing on 2023/4/13.
 * https://support.huaweicloud.com/api-lts/lts_api_0004.html
 */
public class HuaweiLogClient extends AbstractClient {
    public static final String EMPTY_SIGN = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
    public static final String ISO_8601_DAY_TIME_PRO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'";
    public static final String ISO_8601_DAY_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
    public static final String ISO_8601_DAY_TIME = "yyyyMMdd'T'HHmmss'Z'";
    public static final String TIME_ZONE = "GMT";
    public static final String CONTENT_TYPE = "application/json;charset=UTF-8";

    public static final String LOG_REQUEST_ID = "X-Request-Id";
    public static final String UNESCAPE_JSON = "UNESCAPE_JSON";

    @Override
    protected byte[] buildRawBody(String uri, String providerId, Map<String, Object> bizParams, Map<String, Object> accessParams) {
        if (bizParams == null || bizParams.isEmpty()) {
            return new byte[0];
        } else {
            return JSON.toJSONBytes(bizParams);
        }
    }

    @Override
    protected byte[] buildEncodeBody(String uri, String providerId, Map<String, Object> bizParams, Map<String, Object> accessParams, byte[] rawBody) {
        return rawBody;
    }

    @Override
    protected Map<String, String> buildHeader(Method method, String uri, String providerId, Map<String, Object> bizParams,
                                              Map<String, Object> accessParams, byte[] rawBody, byte[] encodeBody) {
        HuaweiLogConfig config = HuaweiLogConfigHolder.get().select(providerId);
        if (config == null) {
            throw new RuntimeException("huawei log config not found by " + providerId);
        }
        Map<String, String> header = new TreeMap<>();
        header.put("Content-Type", CONTENT_TYPE);
        header.put("X-Sdk-Date", getISO8601Date(new Date()));
        return header;
    }

    @Override
    protected String buildSign(Method method, String uri, String providerId, Map<String, Object> bizParams,
                               Map<String, Object> accessParams, Map<String, String> header, byte[] rawBody, byte[] encodeBody) {
        HuaweiLogConfig config = HuaweiLogConfigHolder.get().select(providerId);
        if (config == null) {
            throw new RuntimeException("huawei log config not found by " + providerId);
        }
        String bodySign = bodySign(encodeBody);
        String url = config.getAccountEndpoint() + uri;
        return headSign(method.name(), bodySign, url, header, config.getAccessKeyId(), config.getAccessKeySecret());
    }

    @Override
    protected void loadSign(String providerId, Map<String, String> header, String sign) {
        header.put("Authorization", sign);
    }

    @Override
    protected String buildUrl(String uri, String providerId, Map<String, Object> bizParams, Map<String, Object> accessParams, String sign) {
        HuaweiLogConfig config = HuaweiLogConfigHolder.get().select(providerId);
        if (config == null) {
            throw new RuntimeException("huawei log config not found by " + providerId);
        }
        return config.getAccountEndpoint() + uri;
    }

    @Override
    protected JSONObject parse(Map<String, String> postResult, Map<String, Object> accessParams) {
        JSONObject output = new JSONObject();
        output.put(LOG_REQUEST_ID, postResult.get(LOG_REQUEST_ID));
        boolean unescapeJson = checkUnescapeJson(accessParams);
        String content = unescapeJson ? doUnescapeJson(postResult.get(FireHttpUtils.RESPONSE_BODY)) : postResult.get(FireHttpUtils.RESPONSE_BODY);
        JSONObject obj = JSON.parseObject(content);
        if (obj != null && !obj.isEmpty()) {
            output.putAll(obj);
        }
        return output;
    }

    @Override
    protected boolean checkBody(JSONObject output, boolean skipCheckBodyStatus) {
        return true;
    }

    @Override
    protected boolean checkSign(String providerId, Map<String, String> rawOutput, JSONObject output) {
        return true;
    }

    /**
     * 签名步骤
     * 1.拼接method，url，header，body
     * 2.拼接签名方式，时间戳，第一步的字符串做一次sha256获得签名串
     * 3.拼接签名方式，accessKeyId，headerNames，第二步的拼接字符串做sha256获得签名串
     */
    protected String headSign(String method, String bodySign, String url, Map<String, String> header, String accessKeyId, String accessKeySecret) {
        // 1.build url block
        URL urlObject = newUrl(url);
        String[] pathString = urlObject.getPath().split("/");
        StringBuilder urlBlock = new StringBuilder();
        for (String path : pathString) {
            urlBlock.append(URLEncodeHelper.percentEncode(path)).append("/");
        }
        urlBlock.append("\n");
        urlBlock.append(isBlank(urlObject.getQuery()) ? "" : urlObject.getQuery());

        // 2.build header block
        Map<String, String> allHeaders = new TreeMap<>();
        for (Map.Entry<String, String> entry : header.entrySet()) {
            if (entry.getKey().startsWith("Content-Type") && entry.getValue().startsWith("multipart/form-data")) {
                continue;
            }
            allHeaders.put(entry.getKey().toLowerCase(), entry.getValue());
        }
        StringBuilder headBlock = new StringBuilder();
        for (Map.Entry<String, String> entry : allHeaders.entrySet()) {
            headBlock.append(entry.getKey()).append(":").append(entry.getValue());
            headBlock.append("\n");
        }
        headBlock.append("\n");
        String signHeaderNames = String.join(";", allHeaders.keySet());
        headBlock.append(signHeaderNames);

        // 3.build rawText
        StringBuilder rawText = new StringBuilder();
        rawText.append(method).append("\n")
                .append(urlBlock).append("\n")
                .append(headBlock).append("\n")
                .append(bodySign);

        // 4.build sign string
        String rawString = ByteHelper.byteToHex(HmacSHA256.sha256(rawText.toString()));
        StringBuilder middleText = new StringBuilder();
        middleText.append("SDK-HMAC-SHA256").append("\n")
                .append(allHeaders.get("x-sdk-date")).append("\n")
                .append(rawString);

        // 5.signature
        byte[] keySecret = String.valueOf(accessKeySecret).getBytes(StandardCharsets.UTF_8);
        byte[] signatureByte = HmacSHA256.hmac(keySecret, middleText.toString());
        String signature = ByteHelper.byteToHex(signatureByte);

        // 6.build authorization
        StringBuilder authorization = new StringBuilder();
        authorization.append("SDK-HMAC-SHA256 Access=").append(accessKeyId)
                .append(",SignedHeaders=").append(signHeaderNames)
                .append(",Signature=").append(signature);
        return authorization.toString();
    }

    protected String bodySign(byte[] body) {
        if(body == null || body.length ==0){
            return EMPTY_SIGN;
        }
        return ByteHelper.byteToHex(HmacSHA256.sha256(body));
    }

    public String getISO8601Date(Date date) {
        SimpleDateFormat df = new SimpleDateFormat(ISO_8601_DAY_TIME);
        df.setTimeZone(new SimpleTimeZone(0, TIME_ZONE));
        return df.format(date);
    }

    public Date parseISO8601(String strDate) {
        if (null == strDate || "".equals(strDate)) {
            return null;
        }

        // The format contains 4 ' symbol
        if (strDate.length() != (ISO_8601_DAY_TIME_PRO_FORMAT.length() - 4)) {
            return null;
        }

        SimpleDateFormat df = new SimpleDateFormat(ISO_8601_DAY_TIME_PRO_FORMAT);
        df.setTimeZone(new SimpleTimeZone(0, TIME_ZONE));
        try {
            return df.parse(strDate);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    private URL newUrl(String url) {
        try {
            return new URL(url);
        } catch (MalformedURLException e) {
            throw new RuntimeException(String.format("URL failed: exception: %s", e));
        }
    }

    private boolean checkUnescapeJson(Map<String, Object> accessParams) {
        if (accessParams == null || accessParams.isEmpty() || !accessParams.containsKey(UNESCAPE_JSON)) {
            return false;
        }
        return Boolean.parseBoolean(String.valueOf(accessParams.get(UNESCAPE_JSON)));
    }

    private String doUnescapeJson(String json) {
        String content = StringEscapeUtils.unescapeJson(json);
        if (content.startsWith("\"{")) {
            return content.substring(1, content.lastIndexOf("\""));
        } else {
            return content;
        }
    }
}
